package edu.unika.aifb.graphindex.index;

/**
 * Copyright (C) 2009 Lei Zhang (beyondlei at gmail.com)
 * 
 * This file is part of the graphindex project.
 *
 * graphindex is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2
 * as published by the Free Software Foundation.
 * 
 * graphindex is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with graphindex.  If not, see <http://www.gnu.org/licenses/>.
 */

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;
import org.openrdf.model.vocabulary.RDF;

import edu.unika.aifb.graphindex.data.Table;
import edu.unika.aifb.graphindex.storage.DataField;
import edu.unika.aifb.graphindex.storage.IndexDescription;
import edu.unika.aifb.graphindex.storage.IndexStorageReader;
import edu.unika.aifb.graphindex.storage.StorageException;
import edu.unika.aifb.graphindex.storage.lucene.LuceneIndexStorageReader;

public class DataIndex extends Index {
	private List<IndexDescription> m_indexes;
	private Map<IndexDescription,IndexStorageReader> m_indexStorageReaders;

	private static final Logger log = Logger.getLogger(DataIndex.class);
	
	public interface NodeListener {
		public void node(String node);
	}
	
	public DataIndex(IndexDirectory idxDirectory, IndexConfiguration idxConfig) throws IOException, StorageException {
		this(idxDirectory, idxConfig, true, true);
	}

	public DataIndex(IndexDirectory idxDirectory, IndexConfiguration idxConfig, boolean openAll, boolean warmup) throws IOException, StorageException {
		super(idxDirectory, idxConfig, openAll ? warmup : false); // disable warmup if indexes should not be opened
		m_indexes = new ArrayList<IndexDescription>();
		m_indexStorageReaders = new HashMap<IndexDescription,IndexStorageReader>();
		if (openAll)
			openAllReaderIndexes();
	}

	public void close() throws StorageException {
		for (IndexStorageReader isr : m_indexStorageReaders.values())
			isr.close();
		m_indexes.clear();
		m_indexStorageReaders.clear();
	}
	
	private void openAllReaderIndexes() throws StorageException {
		for (IndexDescription index : m_idxConfig.getIndexes(IndexConfiguration.DI_INDEXES))
			getIndexStorageReader(index);
	}
	
	public IndexDescription getSuitableIndex(DataField... fields) {
		for (IndexDescription index : m_idxConfig.getIndexes(IndexConfiguration.DI_INDEXES))
			if (index.getIndexFieldMap(fields) != null)
				return index;
		return null;
	}
	
	public IndexStorageReader getSuitableIndexStorageReader(DataField... fields) throws StorageException {
		IndexDescription idx = getSuitableIndex(fields);
		if (idx != null)
			return getIndexStorageReader(idx);
		return null;
	}

	/**
	 * Returns an IndexStorageReader object for the requested index, if available.
	 * 
	 * @param index
	 * @return an IndexStorageReader object or null if the index is not present
	 * @throws StorageException
	 */
	public synchronized IndexStorageReader getIndexStorageReader(IndexDescription index) throws StorageException {
		synchronized (m_indexes) {
			IndexStorageReader is = m_indexStorageReaders.get(index);
			if (is == null) {
				try {
					is = new LuceneIndexStorageReader(new File(m_idxDirectory.getDirectory(IndexDirectory.VP_DIR).getAbsolutePath() + "/" + index.getIndexFieldName()), index);
					m_indexStorageReaders.put(index, is);
				} catch (IOException e) {
					throw new StorageException(e);
				}
			}
			return is;
		}
	}

	public Table<String> getTriples(String s, String p, String o) throws StorageException {
		List<DataField> indexFields = new ArrayList<DataField>();
		Map<DataField,String> indexValues = new HashMap<DataField,String>();

		if (s != null) {
			indexFields.add(DataField.SUBJECT);
			indexValues.put(DataField.SUBJECT, s);
		}
		
		if (p != null) {
			indexFields.add(DataField.PROPERTY);
			indexValues.put(DataField.PROPERTY, p);
		}
		
		if (o != null) {
			indexFields.add(DataField.OBJECT);
			indexValues.put(DataField.OBJECT, o);
		}
		
		if (indexFields.size() == 3) {
			indexFields.remove(DataField.OBJECT);
			indexValues.remove(DataField.OBJECT);

			IndexDescription index = getSuitableIndex(indexFields.toArray(new DataField[] {}));
			Table<String> t = getIndexStorageReader(index).getTable(new DataField[] { DataField.SUBJECT, DataField.PROPERTY, DataField.OBJECT }, index.createValueArray(indexValues));
			
			// check if the object is contained, if yes the triple exist and we return it, otherwise return empty table
			Table<String> out = new Table<String>(new String[] { "s", "p", "o" });
			for (String[] row : t) {
				if (row[2].equals(o)) {
					out.addRow(row);
					break;
				}
			}
			return out;
		}
		
		IndexDescription index = getSuitableIndex(indexFields.toArray(new DataField[] {}));
		if (index == null) {
			throw new UnsupportedOperationException("no suitable index found");
		}
		
		return getIndexStorageReader(index).getTable(new DataField[] { DataField.SUBJECT, DataField.PROPERTY, DataField.OBJECT }, index.createValueArray(indexValues));
	}
	
	public Set<String> getSubjectNodes(String property) throws StorageException {
		IndexDescription index = getSuitableIndex(DataField.PROPERTY);
		return getIndexStorageReader(index).getDataSet(DataField.SUBJECT, index.createValueArray(DataField.PROPERTY, property));
	}

	public Set<String> getObjectNodes(String property) throws StorageException {
		IndexDescription index = getSuitableIndex(DataField.PROPERTY);
		return getIndexStorageReader(index).getDataSet(DataField.OBJECT, index.createValueArray(DataField.PROPERTY, property));
	}
	
	public boolean isEntity(String entityId) throws StorageException{
		return getIndexStorageReader(IndexDescription.SPO).getDataItem(entityId, RDF.TYPE.toString()) != null;
	}
	
	public void iterateNodes(String property, NodeListener nl) throws StorageException {
		for (String node : getSubjectNodes(property))
			nl.node(node);
		for (String node : getObjectNodes(property))
			nl.node(node);
	}
	
	public Iterator<String[]> iterator(String property) throws StorageException {
		IndexDescription index = getSuitableIndex(DataField.PROPERTY);
		return getIndexStorageReader(index).iterator(new DataField[] { DataField.SUBJECT, DataField.PROPERTY, DataField.OBJECT }, property);
	}
}
